/*****************************************************************************
 *   Copyright (C) 2008 John Schember <john@nachtimwald.com>                 *
 *                                                                           *
 *   This file is part of niwbillmanager.                                    *
 *                                                                           *
 *   niwbillmanager is free software: you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License as          *
 *   published by the Free Software Foundation, either version 3 of the      *
 *   License, or (at your option) any later version.                         *
 *                                                                           *
 *   niwbillmanager is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the            *
 *   GNU General Public License for more details.                            *
 *                                                                           *
 *   You should have received a copy of the GNU General Public License       *
 *   along with niwbillmanager. If not, see                                  *
 *   <http://www.gnu.org/licenses/>.                                         *
 *****************************************************************************/

#include "datastore.h"

#include <QSqlError>
#include <QUuid>
#include <QVariant>

#include <transaction.h>

QString UndoRedoType::undoRedoTypeToString(UndoRedoType::UndoRedoTypes
    undoRedoType)
{
    QString undoRedoString = "";

    switch (undoRedoType)
    {
        case UndoRedoType::UNDO:
            undoRedoString = "undo";
            break;
        case UndoRedoType::REDO:
            undoRedoString = "redo";
            break;
        default:
            break;
    }

    return undoRedoString;
}

QString QueryType::queryTypeToString(QueryType::QueryTypes queryType)
{
    QString queryTypeString = "";

    switch (queryType)
    {
        case QueryType::AND:
            queryTypeString = "AND";
            break;
        case QueryType::OR:
            queryTypeString = "OR";
            break;
        default:
            break;
    }

    return queryTypeString;
}

DataStore::DataStore()
{
    m_dbName = "libniwbillmanager-" + QUuid::createUuid().toString();
    m_db = QSqlDatabase::addDatabase("QSQLITE", m_dbName);
    m_db.setDatabaseName(":memory:");

    m_db.open();

    m_query = new QSqlQuery(m_db);

    // Create the tables we are storing data in.
    m_query->exec("CREATE TABLE collection \
        (id integer primary key, amountdue TEXT,  autopay TEXT, \
        datedue TEXT, payee TEXT, paymentlocation TEXT, paymentmethod TEXT, \
        paymentrecurringperiod TEXT, paid TEXT, name TEXT, notes TEXT, \
        tags TEXT);");
    m_query->exec("CREATE TABLE transactionlog \
        (transactiontype TEXT, id TEXT);");
    m_query->exec("CREATE TABLE undo \
        (transactiontype TEXT, id TEXT, amountdue TEXT,  autopay TEXT, \
        datedue TEXT, payee TEXT, paymentlocation TEXT, paymentmethod TEXT, \
        paymentrecurringperiod TEXT, paid TEXT, name TEXT, notes TEXT, \
        tags TEXT);");
    m_query->exec("CREATE TABLE redo \
        (transactiontype TEXT, id TEXT, amountdue TEXT,  autopay TEXT, \
        datedue TEXT, payee TEXT, paymentlocation TEXT, paymentmethod TEXT, \
        paymentrecurringperiod TEXT, paid TEXT, name TEXT, notes TEXT, \
        tags TEXT);");
}

DataStore::~DataStore()
{
    delete m_query;
    m_query = 0;

    m_db.close();
    m_db = QSqlDatabase();
    QSqlDatabase::removeDatabase(m_dbName);
}

void DataStore::clearCollection()
{
    m_query->exec("DELETE FROM collection;");
    clearRawTransactionLog();
    clearUndoRedoBuffer(UndoRedoType::UNDO);
    clearUndoRedoBuffer(UndoRedoType::REDO);
}

QString DataStore::addBillItem(BillItem billItem, bool opening)
{
    // Try to insert into the collection
    if (queryInsertBillItem(billItem))
    {
        QString insertId = m_query->lastInsertId().toString();

        // If this is part of opening a collection we don't want to add an
        // undo or a transaction for this add.
        if (!opening) {
            // Insert the undo command.
            billItem.setId(insertId);
            addUndoRedoItem(UndoRedoType::UNDO, TransactionType::DELETE,
                billItem);

            clearUndoRedoBuffer(UndoRedoType::REDO);

            // Update the transaction log.
            addTransactionItem(TransactionType::INSERT, insertId);
        }

        return insertId;
    }

    return QString();
}

bool DataStore::modifyBillItem(BillItem billItem)
{
    BillItem oldBillItem;

    // Store the old item data for use in the undo buffer.
    if (!getBillItemById(billItem.getId(), oldBillItem)) {
        return false;
    }

    if (!queryUpdateBillItem(billItem)) {
        return false;
    }

    // Put the old bill item into undo.
    addUndoRedoItem(UndoRedoType::UNDO, TransactionType::UPDATE, oldBillItem);
    clearUndoRedoBuffer(UndoRedoType::REDO);

    // Update the transaction log.
    addTransactionItem(TransactionType::UPDATE, billItem.getId());

    return true;
}

bool DataStore::deleteBillItem(QString billId)
{
    BillItem billItem;

    if (getBillItemById(billId, billItem)) {
        // Put the bill item into undo.
        addUndoRedoItem(UndoRedoType::UNDO, TransactionType::INSERT, billItem);
        clearUndoRedoBuffer(UndoRedoType::REDO);

        // Delete the item from collection.
        queryDeleteBillItem(billId);

        // Update the transaction log.
        addTransactionItem(TransactionType::DELETE, billItem.getId());

        return true;
    }

    return false;
}

bool DataStore::getBillItemById(QString billId, BillItem &billItem)
{
    // Make sure the query is valied. Is the bill id valid.
    if (m_query->exec("SELECT id, amountdue, autopay, datedue, payee, \
        paymentlocation, paymentmethod, paymentrecurringperiod, paid, name, \
        notes, tags FROM collection WHERE id='" + billId + "';"))
    {
        return getNextBillItem(billItem);
    }

    return true;
}

void DataStore::startGetBillItemsFromTags(QSet<QString> tags)
{
    QString sqlString;

    if (!tags.isEmpty()) {
        sqlString = " ( ";

        Q_FOREACH (QString item, tags) {
            sqlString += " OR tags LIKE '% " + item + " %' ";
        }
        sqlString.remove(4, 3);

        if (!sqlString.isEmpty()) {
            sqlString.prepend(" WHERE ");
        }
    }
    else {
        sqlString = " WHERE tags LIKE '%  %'";
    }

    m_query->exec("SELECT id, amountdue, autopay, datedue, payee, \
        paymentlocation, paymentmethod, paymentrecurringperiod, paid, name, \
        notes, tags FROM collection " + sqlString + ";");
}

void DataStore::startGetBillItemsWithoutTags()
{
    QSet<QString> tags;
    QString tag = "";

    tags.insert(tag);

    startGetBillItemsFromTags(tags);
}

void DataStore::startGetAllBillItems()
{
    m_query->exec("SELECT id, amountdue, autopay, datedue, payee, \
        paymentlocation, paymentmethod, paymentrecurringperiod, paid, name, \
        notes, tags FROM collection");
}

void DataStore::startGetBillItems(QSet<QString> query,
    QueryType::QueryTypes queryType,
    bool amountDue, bool autoPay, bool nonAutoPay,
    bool dateDue, bool payee, bool paymentLocation,
    bool paymentMethod, bool paymentRecurringPeriod,
    bool recurringOnly, bool paid, bool unpaid,
    bool name, bool notes, bool tags)
{
    QSet<QString> itemizedQuery;
    QString sqlString = "";
    QString searchString = "";
    QString queryTypeString = QueryType::queryTypeToString(queryType);
    queryTypeString = " " + queryTypeString + " ";

    Q_FOREACH (QString item, query) {
        item.prepend("'%");
        item.append("%'");
        itemizedQuery.insert(item);
    }

    if (amountDue) {
        searchString = "";
        sqlString += queryTypeString + "( ";
        Q_FOREACH (QString item, itemizedQuery) {
            searchString += " OR amountdue LIKE " + item + " ";
        }
        searchString.remove(0, 4);
        sqlString += searchString + " ) ";
    }
    if (dateDue) {
        searchString = "";
        sqlString += queryTypeString + "( ";
        Q_FOREACH (QString item, itemizedQuery) {
            searchString += " OR datedue LIKE " + item + " ";
        }
        searchString.remove(0, 4);
        sqlString += searchString + " ) ";
    }
    if (payee) {
        searchString = "";
        sqlString += queryTypeString + "( ";
        Q_FOREACH (QString item, itemizedQuery) {
            searchString += " OR payee LIKE " + item + " ";
        }
        searchString.remove(0, 4);
        sqlString += searchString + " ) ";
    }
    if (paymentLocation) {
        searchString = "";
        sqlString += queryTypeString + "( ";
        Q_FOREACH (QString item, itemizedQuery) {
            searchString += " OR paymentlocation LIKE " + item + " ";
        }
        searchString.remove(0, 4);
        sqlString += searchString + " ) ";
    }
    if (paymentMethod) {
        searchString = "";
        sqlString += queryTypeString + "( ";
        Q_FOREACH (QString item, itemizedQuery) {
            searchString += " OR paymentmethod LIKE " + item + " ";
        }
        searchString.remove(0, 4);
        sqlString += searchString + " ) ";
    }
    if (paymentRecurringPeriod) {
        searchString = "";
        sqlString += queryTypeString + "( ";
        Q_FOREACH (QString item, itemizedQuery) {
            searchString += " OR paymentrecurringperiod LIKE " + item + " ";
        }
        searchString.remove(0, 4);
        sqlString += searchString + " ) ";
    }
    if (name) {
        searchString = "";
        sqlString += queryTypeString + "( ";
        Q_FOREACH (QString item, itemizedQuery) {
            searchString += " OR name LIKE " + item + " ";
        }
        searchString.remove(0, 4);
        sqlString += searchString + " ) ";
    }
    if (notes) {
        searchString = "";
        sqlString += queryTypeString + "( ";
        Q_FOREACH (QString item, itemizedQuery) {
            searchString += " OR notes LIKE " + item + " ";
        }
        searchString.remove(0, 4);
        sqlString += searchString + " ) ";
    }
    if (tags) {
        searchString = "";
        sqlString += queryTypeString + "( ";
        Q_FOREACH (QString item, query) {
            searchString += " OR tags LIKE '% " + item + " %' ";
        }
        searchString.remove(0, 4);
        sqlString += searchString + " ) ";
    }

    if (autoPay && !nonAutoPay) {
        sqlString += " AND autopay = 'T' ";
    }
    if (nonAutoPay && !autoPay) {
        sqlString += " AND autopay = 'F' ";
    }
    if (recurringOnly) {
        sqlString += " AND paymentrecurringperiod NOT '' ";
    }
    if (paid && !unpaid) {
        sqlString =+ " AND paid = 'T' ";
    }
    if (unpaid && !paid) {
        sqlString += " AND paid = 'F' ";
    }

    if (sqlString.startsWith(" OR ")) {
        sqlString.remove(0, 4);
    }
    else if(sqlString.startsWith(" AND ")) {
        sqlString.remove(0, 5);
    }

    if (!sqlString.isEmpty()) {
        sqlString.prepend(" WHERE ");
    }

    m_query->exec("SELECT id, amountdue, autopay, datedue, payee, \
        paymentlocation, paymentmethod, paymentrecurringperiod, paid, name, \
        notes, tags FROM collection " + sqlString + ";");
}

bool DataStore::hasMoreBillItems()
{
    return queryHasMore("collection");
}

bool DataStore::getNextBillItem(BillItem &billItem)
{
    // This prevents us from getting non-existant data.
    if (!hasMoreBillItems()) {
        return false;
    }

    // Grab the next item.
    if (!m_query->next()) {
        return false;
    }

    if (!m_query->value(0).toString().isEmpty()) {
        billItem.setId(m_query->value(0).toString());
    }
    if (!m_query->value(1).toString().isEmpty()) {
        billItem.setAmountDue(m_query->value(1).toString());
    }
    if (!m_query->value(2).toString().isEmpty()) {
        billItem.setAutoPay(m_query->value(2).toString()=="T"?true:false);
    }
    if (!m_query->value(3).toString().isEmpty()) {
        billItem.setDateDue(QDate::fromString(
            m_query->value(3).toString(), Qt::ISODate));
    }
    if (!m_query->value(4).toString().isEmpty()) {
        billItem.setPayee(m_query->value(4).toString());
    }
    if (!m_query->value(5).toString().isEmpty()) {
        billItem.setPaymentLocation(m_query->value(5).toString());
    }
    if (!m_query->value(6).toString().isEmpty()) {
        billItem.setPaymentMethod(m_query->value(6).toString());
    }
    if (!m_query->value(7).toString().isEmpty()) {
        billItem.setPaymentRecurringPeriod(m_query->value(7).toString());
    }
    if (!m_query->value(8).toString().isEmpty()) {
        billItem.setPaid(m_query->value(8).toString()=="T"?true:false);
    }
    if (!m_query->value(9).toString().isEmpty()) {
        billItem.setName(m_query->value(9).toString());
    }
    if (!m_query->value(10).toString().isEmpty()) {
        billItem.setNotes(m_query->value(10).toString());
    }
    if (!m_query->value(11).toString().isEmpty()) {
        billItem.setTags(m_query->value(11).toString().split(" ").toSet());
    }

    return true;
}

QSet<QString> DataStore::getAllTags()
{
    QSet<QString> tags;

    m_query->exec("SELECT tags FROM collection;");

    while (m_query->next()) {
        Q_FOREACH (QString tag, m_query->value(0).toString().split(" ")) {
            tags.insert(tag);
        }
    }

    return tags;
}

QSet<QString> DataStore::getTags(QString billId)
{
    m_query->exec("SELECT tags FROM collection WHERE id='" + billId + "';");

    m_query->next();

    return m_query->value(0).toString().split(" ").toSet();
}

bool DataStore::canUndoChange()
{
    return canUndoRedoChange(UndoRedoType::UNDO);
}

bool DataStore::canRedoChange()
{
    return canUndoRedoChange(UndoRedoType::REDO);
}

bool DataStore::undoChange(Transaction &transaction)
{
    return undoRedoChange(UndoRedoType::UNDO, transaction);
}

bool DataStore::redoChange(Transaction &transaction)
{
    return undoRedoChange(UndoRedoType::REDO, transaction);
}

void DataStore::clearRawTransactionLog()
{
    m_query->exec("DELETE FROM transactionlog;");
}

void DataStore::startGetRawTransactionLog()
{
    m_query->exec("SELECT transactiontype, id FROM transactionlog");
}

bool DataStore::hasMoreRawTransactions()
{
    return queryHasMore("transactionlog");
}

ShortTransaction DataStore::getNextRawTransaction()
{
    ShortTransaction transaction;

    m_query->next();

    transaction.setTransactionType(TransactionType::stringToTransactionType(
        m_query->value(0).toString()));
    transaction.setBillId(m_query->value(1).toString());

    return transaction;
}

bool DataStore::queryHasMore(const QString &table)
{
    // Check to see if the last query executed was the select all query.
    // We get the items from this query going though the db with query->next().
    if (!m_query->lastQuery().contains("FROM " + table)) {
        return false;
    }

    // Query must be active for m_query->next() to work.
    if (!m_query->isActive()) {
        return false;
    }

    // Check to see if the next query is avaliable.
    if (!m_query->next()) {
        return false;
    }

    // Non-valid records cannt be retrieved.
    if (!m_query->isValid()) {
        return false;
    }

    // Place the recored back to where it started so readNextBillItem can
    // get the record. QSqlQuery has no peek function. We don't care if the
    // last query if valied because we will only be retrieving the next one.
    m_query->previous();

    return true;
}

bool DataStore::queryInsertBillItem(const BillItem &billItem)
{
    return m_query->exec("INSERT INTO collection (amountdue, \
        autopay, datedue, payee, paymentlocation, paymentmethod, \
        paymentrecurringperiod, paid, name, notes, tags) values ("
        "'" + billItem.getAmountDue() + "', "
        "'" + (billItem.getAutoPay()?"T":"F") + "', "
        "'" + billItem.getDateDue().toString(Qt::ISODate) + "', "
        "'" + billItem.getPayee() + "', "
        "'" + billItem.getPaymentLocation() + "', "
        "'" + billItem.getPaymentMethod() + "', "
        "'" + billItem.getPaymentRecurringPeriod() + "', "
        "'" + (billItem.getPaid()?"T":"F") + "', "
        "'" + billItem.getName() + "', "
        "'" + billItem.getNotes() + "', "
        "' " + BillItem::stringSetToString(billItem.getTags()) + " '"
        ");");
}

bool DataStore::queryInsertBillItemWithId(const BillItem &billItem)
{
    return m_query->exec("INSERT INTO collection (id, amountdue, \
        autopay, datedue, payee, paymentlocation, paymentmethod, \
        paymentrecurringperiod, paid, name, notes, tags) values ("
        "'" + billItem.getId() + "', "
        "'" + billItem.getAmountDue() + "', "
        "'" + (billItem.getAutoPay()?"T":"F") + "', "
        "'" + billItem.getDateDue().toString(Qt::ISODate) + "', "
        "'" + billItem.getPayee() + "', "
        "'" + billItem.getPaymentLocation() + "', "
        "'" + billItem.getPaymentMethod() + "', "
        "'" + billItem.getPaymentRecurringPeriod() + "', "
        "'" + (billItem.getPaid()?"T":"F") + "', "
        "'" + billItem.getName() + "', "
        "'" + billItem.getNotes() + "', "
        "' " + BillItem::stringSetToString(billItem.getTags()) + " '"
        ");");
}

bool DataStore::queryUpdateBillItem(const BillItem &billItem)
{
    // Update the bill item.
    return m_query->exec("UPDATE collection SET "
        "amountdue='" + billItem.getAmountDue() + "', "
        "autopay='" + (billItem.getAutoPay()?"T":"F") + "', "
        "datedue='" + billItem.getDateDue().toString(Qt::ISODate) + "', "
        "payee='" + billItem.getPayee() + "', "
        "paymentlocation='" + billItem.getPaymentLocation() + "', "
        "paymentmethod='" + billItem.getPaymentMethod() + "', "
        "paymentrecurringperiod='" +
            billItem.getPaymentRecurringPeriod() + "', "
        "paid='" + (billItem.getPaid()?"T":"F") + "', "
        "name='" + billItem.getName() + "', "
        "notes='" + billItem.getNotes() + "', "
        "tags=' " + BillItem::stringSetToString(billItem.getTags()) + " ' "
        "WHERE id='" + billItem.getId() + "'"
        ";");
}

bool DataStore::queryDeleteBillItem(const QString &billId)
{
    return m_query->exec("DELETE FROM collection WHERE id='" + billId +
        "';");
}

void DataStore::addTransactionItem(
    TransactionType::TransactionTypes transactionType, const QString &billId)
{
    m_query->exec("INSERT INTO transactionlog (transactiontype, id) \
        values ('"
        + TransactionType::transactionTypeToString(transactionType)
        + "', '" + billId + "');");
}

void DataStore::addUndoRedoItem(UndoRedoType::UndoRedoTypes undoRedoType,
    TransactionType::TransactionTypes transactionType, BillItem billItem)
{
    m_query->exec("INSERT INTO "
        + UndoRedoType::undoRedoTypeToString(undoRedoType) +
        " (transactiontype, id, amountdue, autopay, datedue, payee, \
        paymentlocation, paymentmethod, paymentrecurringperiod, paid, name, \
        notes, tags) values ("
        "'" + TransactionType::transactionTypeToString(transactionType) + "', "
        "'" + billItem.getId() + "', "
        "'" + billItem.getAmountDue() + "', "
        "'" + (billItem.getAutoPay()?"T":"F") + "', "
        "'" + billItem.getDateDue().toString(Qt::ISODate) + "', "
        "'" + billItem.getPayee() + "', "
        "'" + billItem.getPaymentLocation() + "', "
        "'" + billItem.getPaymentMethod() + "', "
        "'" + billItem.getPaymentRecurringPeriod() + "', "
        "'" + (billItem.getPaid()?"T":"F") + "', "
        "'" + billItem.getName() + "', "
        "'" + billItem.getNotes() + "', "
        "'" + BillItem::stringSetToString(billItem.getTags()) + "'"
        ");");
}

void DataStore::removeUndoRedoItem(UndoRedoType::UndoRedoTypes undoRedoType,
    QString rowId)
{
    m_query->exec("DELETE FROM "
        + UndoRedoType::undoRedoTypeToString(undoRedoType) +
        " WHERE ROWID='" + rowId + "';");
}

bool DataStore::canUndoRedoChange(UndoRedoType::UndoRedoTypes undoRedoType)
{
    m_query->exec("SELECT id FROM " +
        UndoRedoType::undoRedoTypeToString(undoRedoType) + ";");

    if (m_query->next()) {
        return true;
    }

    return false;
}

bool DataStore::undoRedoChange(const UndoRedoType::UndoRedoTypes &undoRedoType,
    Transaction &transaction)
{
    if (!canUndoRedoChange(undoRedoType)) {
        return false;
    }

    UndoRedoType::UndoRedoTypes oppositeUndoRedoType;

    switch (undoRedoType)
    {
        case UndoRedoType::UNDO:
            oppositeUndoRedoType = UndoRedoType::REDO;
            break;
        case UndoRedoType::REDO:
            oppositeUndoRedoType = UndoRedoType::UNDO;
            break;
        default:
            return false;
            break;
    }

    BillItem billItem;
    QString rowId;

    getLastUndoRedoItem(undoRedoType, transaction, rowId);

    switch (transaction.getTransactionType())
    {
        case TransactionType::INSERT:
            billItem.setId(transaction.getBillItem().getId());
            if (!queryInsertBillItemWithId(transaction.getBillItem())) {
                return false;
            }
            addUndoRedoItem(oppositeUndoRedoType, TransactionType::DELETE,
                billItem);
            break;
        case TransactionType::UPDATE:
            getBillItemById(transaction.getBillItem().getId(), billItem);
            if (!queryUpdateBillItem(transaction.getBillItem())) {
                return false;
            }
            addUndoRedoItem(oppositeUndoRedoType, TransactionType::UPDATE,
                billItem);
            break;
        case TransactionType::DELETE:
            getBillItemById(transaction.getBillItem().getId(), billItem);
            if (!queryDeleteBillItem(transaction.getBillItem().getId())) {
                return false;
            }
            addUndoRedoItem(oppositeUndoRedoType, TransactionType::INSERT,
                billItem);
            break;
        default:
            return false;
            break;
    }

    // Remove the item from the buffer.
    removeUndoRedoItem(undoRedoType, rowId);

    return true;
}

bool DataStore::getLastUndoRedoItem(UndoRedoType::UndoRedoTypes undoRedoType,
    Transaction &transaction, QString &rowId)
{
    // Get the last bill item from the buffer.
    m_query->exec("SELECT transactiontype, id, amountdue,  autopay, \
        datedue, payee, paymentlocation, paymentmethod, \
        paymentrecurringperiod, paid, name, notes, tags, ROWID FROM " +
        UndoRedoType::undoRedoTypeToString(undoRedoType) + ";");

    if (!m_query->last()) {
        return false;
    }

    BillItem billItem;
    QString transactionString = m_query->value(0).toString();

    // Make sure there is a Transaction Type.
    if (transactionString.isEmpty()) {
        return false;
    }

    // Load all the data from the last bill item into a bill item object.
    if (!m_query->value(1).toString().isEmpty()) {
        billItem.setId(m_query->value(1).toString());
    }
    else {
        return false;
    }
    if (!m_query->value(2).toString().isEmpty()) {
        billItem.setAmountDue(m_query->value(2).toString());
    }
    if (!m_query->value(3).toString().isEmpty()) {
        billItem.setAutoPay(m_query->value(3).toString()=="T"?true:false);
    }
    if (!m_query->value(4).toString().isEmpty()) {
        billItem.setDateDue(QDate::fromString(
            m_query->value(4).toString(), Qt::ISODate));
    }
    if (!m_query->value(5).toString().isEmpty()) {
        billItem.setPayee(m_query->value(5).toString());
    }
    if (!m_query->value(6).toString().isEmpty()) {
        billItem.setPaymentLocation(m_query->value(6).toString());
    }
    if (!m_query->value(7).toString().isEmpty()) {
        billItem.setPaymentMethod(m_query->value(7).toString());
    }
    if (!m_query->value(8).toString().isEmpty()) {
        billItem.setPaymentRecurringPeriod(m_query->value(8).toString());
    }
    if (!m_query->value(9).toString().isEmpty()) {
        billItem.setPaid(m_query->value(9).toString()=="T"?true:false);
    }
    if (!m_query->value(10).toString().isEmpty()) {
        billItem.setName(m_query->value(10).toString());
    }
    if (!m_query->value(11).toString().isEmpty()) {
        billItem.setNotes(m_query->value(11).toString());
    }
    if (!m_query->value(12).toString().isEmpty()) {
        billItem.setTags(m_query->value(12).toString().split(" ").toSet());
    }

    // Get the row id for this item so it can be removed later.
    if (m_query->value(13).toString().isEmpty()) {
        return false;
    }
    rowId = m_query->value(13).toString();

    // Set the transaction type.
    transaction.setTransactionType(TransactionType::stringToTransactionType(
        transactionString));
    // Save the bill item object in the transaction object.
    transaction.setBillItem(billItem);

    return true;
}

void DataStore::clearUndoRedoBuffer(UndoRedoType::UndoRedoTypes undoRedoType)
{
    m_query->exec("DELETE FROM "
        + UndoRedoType::undoRedoTypeToString(undoRedoType) + ";");
}
